#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <sys/time.h>
#include "aceMesh_runtime_c.h"
#include "gauss_aceMesh.h"
//#define N 258
double A[N][N];
#ifdef CHECK
double test[N][N];
#endif

extern void slave_stencil_core(targs* args);

#ifdef INST_PERF
unsigned long inst_count[64]; 
#endif

void my_neighbors(void** neighbor_addrs,int* num_neigh, targs* args)
{
   int starti=args->starti;
    int startj=args->startj;
    void* src_addr=args->src_addr;
  
    *num_neigh=0;
    if(starti - BS>=0)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BS*N * 8);
    if(starti + BS < N)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BS*N * 8);
    if(startj - BS >= 0)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BS * 8);
    if(startj + BS < N)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BS* 8);
}    

int gauss_seidel(const int bs,const int L)
{
    targs each;
	int ii,jj;
	int iter;
    for(iter = 0; iter < L; ++iter)
    {
        for(ii = 1; ii <= N -2; ii += bs)
        {
            int endi = (ii + bs > N - 2 ) ? N - 2 : ii + bs;
            for(jj = 1; jj <= N - 2; jj += bs) 
            {
                
           acemesh_begin_split_task("gauss_seidel");
                int endj = (jj + bs > N - 2 ) ? N - 2 : jj + bs;
                each.starti=ii;
                each.endi=endi;
                each.startj=jj;
                each.endj=endj;
                each._BS=bs;
                each.iter=iter;
                each.src_addr=&A[ii][jj];
                acemesh_push_wlist(1, &A[ii][jj],NORMAL);
                acemesh_push_rlist(1, &A[ii][jj], NORMAL);
                
                acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                      (void*)(&each), sizeof(targs), 
                      NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
                acemesh_task_set_type(STENCIL_TASK);
            acemesh_end_split_task();
        
            }
        }
    }
    acemesh_spawn_and_wait(1);
    return 0;
}



void Init()
{
		int i,j;
		for(i = 0;i < N;i++)
			for(j = 0;j < N;j++)
			{
				A[i][j] = (double)(i*2.5 + j*3.3 ) /3.0;
			
			}
				
}
#ifdef CHECK
int check(const int ITER,const int bs)
{
	int i,j,itr; 
	printf("begin check\n");
    for(i = 0; i < N; i++) 
    for(j = 0; j < N; j++)
          test[i][j] = (double)(i*2.5 + j*3.3 ) /3.0; 
   for(itr = 0; itr < ITER; ++itr)
    {
        for(i = 1; i < N-1 ; i++)
        for(j = 1; j < N-1 ; j++)
           	test[i][j] = 0.2* (test[i][j] + test[i-1][j]+test[i+1][j] + test[i][j-1] + test[i][j+1]);
    }

    for(i = 1; i < N - 1; i++)
    for(j = 1; j < N - 1; j++)
       if( fabs(test[i][j]  - A[i][j]) > 1e-11 )
       {
		   printf("i : %d,j : %d\n",i,j);
		   printf("test : %lf\n",test[i][j]);
		   printf("A : %lf\n",A[i][j]);
           return 1;
       }
	printf("correct\n");
    return 0;
}
#endif


int main(int argc, char** argv)
{
    int num_threads;
    struct timeval start;
    struct timeval end;
    double total_time;       
    
    if(argc > 5)
    {
        printf("input arg error\n");
        return 0;
    }
    num_threads = 1;
    int bs = 16;
    int iter = 1;
    if(argc > 1)
        bs = atoi(argv[1]);
    if(argc > 2)
        iter = atoi(argv[2]);
    if(argc > 3)
        num_threads = atoi(argv[3]);
    if(((N-2)%BS) != 0)
    {   
      printf("(N-2)%BS) must == 0 \n");
      assert(0);
    }
#ifdef INST_PERF
    int i;
    penv_slave0_inst_init();
    for(i=0;i<num_threads;i++)
    {
        inst_count[i]=0;      
    }
#endif
    Init();
    acemesh_runtime_init(num_threads);
    
	gettimeofday(&start,NULL);
    gauss_seidel(BS,iter);
    gettimeofday(&end,NULL);   
    
	acemesh_runtime_shutdown();  
    total_time = 0.0;
    total_time+=(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000000.0;
    printf("total time: %lf\n",total_time);
#ifdef INST_PERF
    unsigned long total_inst_count=0;
    for(i=0;i<num_threads;i++)
    {
       total_inst_count += inst_count[i];
       printf("every= %lu \t",inst_count[i]);
    }
    printf("total inst_count= %lu\n",total_inst_count);
#endif
#ifdef CHECK
    
	gettimeofday(&start,NULL);
    if(check(iter,BS))
    { 
        printf("check_error\n");
        exit(1);
    }  
    gettimeofday(&end,NULL);   
    total_time=(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000000.0;
    printf("serial-total time: %lf\n",total_time);
#endif  
    return 0;
}
